package hsl.study.videocapturesystem.service.impl;


import hsl.study.videocapturesystem.config.VideoCaptureProperties;
import hsl.study.videocapturesystem.service.VideoCaptureSystemWithJpgService;
import hsl.study.videocapturesystem.test.VideoCaptureSystem;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import org.openjdk.jol.info.GraphLayout;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.content.Media;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.ollama.api.OllamaModel;
import org.springframework.ai.ollama.api.OllamaOptions;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.stereotype.Service;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.Instant;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @Description:
 * @Author: hsl
 * @CreateDate: 2025/7/29 9:46
 * @Version: 1.0
 */

@Service
public class VideoCaptureSystemWithJpgServiceImpl implements VideoCaptureSystemWithJpgService {

    private static final Logger LOGGER = Logger.getLogger(VideoCaptureSystem.class.getName());

    @Resource
    private OllamaChatModel ollamaChatModel;

    // 配置参数
    @Resource
    private VideoCaptureProperties videoCaptureProperties;

    // 注入自定义线程池
    @Resource
    private ThreadPoolExecutor threadPoolExecutor;

    private BlockingQueue<CapturedImage> imageQueue;
    private String videoUrl;
    private String outputDir;
    private int frequency;
    // AI结果输出文件路径（从配置读取）
    private String aiResultFile;
    // 保存FFmpeg进程引用，用于关闭
    private Process ffmpegProcess;
    // 保存捕获线程引用，用于优雅关闭
    private Thread captureThread;


    /**
     * Spring初始化后执行参数初始化
     */
    @PostConstruct
    public void init() {
        // 从配置类获取参数
        this.videoUrl = videoCaptureProperties.getVideoUrl();
        this.outputDir = videoCaptureProperties.getOutputDir();
        this.frequency = videoCaptureProperties.getFrequency();
        this.aiResultFile = videoCaptureProperties.getAiResultFile();
        int queueCapacity = videoCaptureProperties.getQueueCapacity();

        // 初始化阻塞队列
        this.imageQueue = new LinkedBlockingQueue<>(queueCapacity);

        // 初始化AI结果输出目录（确保父目录存在）
        initAiResultDirectory();
    }

    // 初始化AI结果文件的父目录
    private void initAiResultDirectory() {
        try {
            Path resultFilePath = Paths.get(aiResultFile);
            // 若父目录不存在则创建
            if (resultFilePath.getParent() != null && !Files.exists(resultFilePath.getParent())) {
                Files.createDirectories(resultFilePath.getParent());
            }
            // 初始化文件（若不存在则创建空文件）
            if (!Files.exists(resultFilePath)) {
                Files.createFile(resultFilePath);
            }
        } catch (IOException e) {
            LOGGER.log(Level.SEVERE, "初始化AI结果文件目录失败", e);
            throw new RuntimeException("初始化AI结果文件目录失败", e);
        }
    }

    private void initOutputDirectory() {
        try {
            // 创建输出目录 将输出目录路径转为Path对象
            Path dirPath = Paths.get(outputDir);
            if (!Files.exists(dirPath)) {
                Files.createDirectories(dirPath);
            }
        } catch (IOException e) {
            LOGGER.log(Level.SEVERE, "初始化输出目录失败", e);
            throw new RuntimeException("初始化输出目录失败", e);
        }
    }

    /**
     * 启动视频捕获流程
     */
    public void start() {
        // 使用自定义线程池提交任务
        // 1. captureFrames：捕获视频帧并放入队列
        // 2. saveImages：从队列取帧并保存为图片
        // 3. logStatus：定期打印队列状态
        captureThread = new Thread(this::captureFrames);
        threadPoolExecutor.submit(captureThread);
        // 2. 从队列取帧并调用AI处理（替换原saveImages）
        threadPoolExecutor.submit(this::processImagesWithAI);
//        threadPoolExecutor.submit(this::saveImages);
        threadPoolExecutor.submit(this::logStatus);
    }

    private void captureFrames() {
        int maxRetries = 1;  // 增加最大重试次数
        int retryCount = 0;
        long lastRetryTime = 0;
        final long MIN_RETRY_INTERVAL = 5000;  // 最小重试间隔（毫秒）

        while (retryCount < maxRetries && !Thread.currentThread().isInterrupted()) {
            try {
                LOGGER.info("准备启动FFmpeg，尝试次数: " + (retryCount + 1));

                // 构建FFmpeg命令：从视频流按指定帧率截取图像，通过管道输出JPEG
                ProcessBuilder pb = new ProcessBuilder(
                        "ffmpeg",  // FFmpeg可执行文件（需确保系统环境变量中存在ffmpeg）
                        "-hide_banner",  // 隐藏FFmpeg启动横幅信息
                        "-loglevel", "error",  // 日志级别：只输出错误信息
                        "-fflags", "+genpts+discardcorrupt",  // 允许生成时间戳，丢弃损坏的数据包
                        "-flags", "+low_delay",               // 低延迟模式
                        "-probesize", "32",                   // 减小探测大小，加快启动
                        "-analyzeduration", "0",              // 不分析时长，适用于实时流
                        "-rtsp_transport", "tcp",             // 使用TCP而非UDP（减少丢包）
                        "-i", videoUrl,  // 输入源：视频地址（如rtsp://xxx或本地文件）
                        "-r", String.valueOf(frequency),  // 帧率：截取频率 frequency
                        "-f", "image2pipe",  // 输出格式：图像管道（通过标准输出流传递图像数据）
                        "-vcodec", "mjpeg",  // 编码格式：JPEG（确保输出为JPG图像）
                        "-q:v", "15",         // JPEG质量参数：1-31，1为最高质量
                        "-"  // 输出目标：标准输出流（通过管道传递给当前程序）
                );
                pb.redirectErrorStream(false);  // 不合并错误流和输出流（单独处理错误）
                ffmpegProcess = pb.start();  // 启动FFmpeg进程

                LOGGER.info("FFmpeg进程已启动，PID: " + ffmpegProcess.hashCode());

                // 单独启动线程处理FFmpeg的错误输出（避免错误流阻塞）
                new Thread(() -> {
                    try (BufferedReader errorReader = new BufferedReader(
                            new InputStreamReader(ffmpegProcess.getErrorStream()))) {  // 读取错误流
                        String errorLine;

                        int decodeErrorCount = 0;  // 解码错误计数器
                        final int MAX_DECODE_ERRORS = 10;  // 连续解码错误上限

                        while ((errorLine = errorReader.readLine()) != null) {
                            // 记录FFmpeg错误信息
                            LOGGER.severe("FFmpeg错误: " + errorLine);

                            // 检测H.264解码错误
                            if (errorLine.contains("left block unavailable") ||
                                    errorLine.contains("error while decoding MB")) {
                                decodeErrorCount++;

                                // 如果连续出现多次解码错误，主动中断捕获线程
                                if (decodeErrorCount >= MAX_DECODE_ERRORS) {
                                    LOGGER.severe("连续出现" + MAX_DECODE_ERRORS + "次解码错误，准备重启FFmpeg");
                                    // 发生错误时中断当前线程（终止捕获流程）
                                    Thread.currentThread().interrupt();
                                    if (captureThread != null) {
                                        captureThread.interrupt();
                                    }
                                    break;
                                }
                            } else {
                                // 非解码错误，重置计数器
                                decodeErrorCount = 0;
                            }
                        }
                    } catch (IOException e) {
                        LOGGER.log(Level.WARNING, "FFmpeg错误流读取失败", e);
                    }
                }, "FFmpeg-Error-Handler").start();  // 线程名称：FFmpeg-Error-Handler

                // JPEG文件签名：开始标记(SOI)和结束标记(EOI)
                byte[] jpegStartSignature = {(byte) 0xFF, (byte) 0xD8}; // JPEG开始标记(SOI)
                byte[] jpegEndSignature = {(byte) 0xFF, (byte) 0xD9};   // JPEG结束标记(EOI)

                // 读取FFmpeg输出的图像数据（标准输出流）
                try (InputStream in = ffmpegProcess.getInputStream()) {
                    LOGGER.info("开始从FFmpeg读取输出流...");

                    byte[] buffer = new byte[8192];  // 缓冲区：用于读取字节流（8KB）
                    int bytesRead;  // 每次读取的字节数
                    // 图像缓冲区：用于临时存储一帧JPEG的字节数据
                    ByteArrayOutputStream imageBuffer = new ByteArrayOutputStream();
                    boolean isCollecting = false;  // 是否正在收集一帧图像数据（初始为false）
                    int frameCount = 0;  // 记录捕获的帧数

                    // 循环读取输入流，直到线程被中断或流结束
                    while (!Thread.currentThread().isInterrupted()
                            && (bytesRead = in.read(buffer)) != -1) {
                        // 遍历缓冲区中的字节，检测JPEG签名以分割帧
                        for (int i = 0; i < bytesRead; i++) {
                            // 未开始收集时，检测JPEG开始签名（SOI）
                            if (!isCollecting && i + 1 < bytesRead) {
                                boolean match = buffer[i] == jpegStartSignature[0]
                                        && buffer[i + 1] == jpegStartSignature[1];

                                if (match) {  // 匹配成功：找到一帧的起始位置
                                    imageBuffer.reset();  // 重置缓冲区
                                    // 将从签名开始的剩余字节写入图像缓冲区
                                    imageBuffer.write(buffer, i, bytesRead - i);
                                    isCollecting = true;  // 标记为"正在收集"
                                    LOGGER.fine("检测到JPEG帧开始，位置: " + i);
                                    break;  // 跳出循环，等待下一次读取
                                }
                            } else if (isCollecting) {  // 正在收集时，检测JPEG结束签名（EOI）
                                // 将当前字节写入图像缓冲区
                                imageBuffer.write(buffer, i, 1);

                                // 检测是否出现JPEG结束签名（EOI）
                                if (i + 1 < bytesRead) {
                                    boolean endMatch = buffer[i] == jpegEndSignature[0]
                                            && buffer[i + 1] == jpegEndSignature[1];

                                    if (endMatch) {  // 检测到结束签名：当前帧数据完整
                                        // 写入结束标记的第二个字节
                                        imageBuffer.write(buffer[i + 1]);

                                        byte[] imageData = imageBuffer.toByteArray();  // 取出当前帧数据
                                        frameCount++;

                                        // 生成图片名称：当前时间戳（毫秒级，确保唯一）
                                        String imageName = Instant.now().toEpochMilli() + ".jpg";

                                        // ====== 核心修改：队列满时移除最旧元素，再添加新元素 ======
                                        synchronized (imageQueue) {  // 确保线程安全
                                            // 1. 若队列满，移除最旧元素（头部元素）
                                            if (imageQueue.remainingCapacity() == 0) {
                                                CapturedImage oldestImage = imageQueue.poll();  // 移除最旧元素
                                                if (oldestImage != null) {
                                                    LOGGER.warning("队列满，移除最旧帧: " + oldestImage.getName());

                                                    // 记录满队列内存
                                                    LOGGER.warning("队列满时内存占用: " + calculateFullQueueMemory() + " bytes");

                                                    // 记录真实内存占用
                                                    long actualMemory = calculateActualQueueMemory();
                                                    LOGGER.warning("队列满内存占用: " + actualMemory + " bytes ("
                                                            + (actualMemory / (1024 * 1024)) + " MB)");

                                                }
                                            }

                                            // 2. 添加新元素（此时队列必有容量）
                                            boolean added = imageQueue.offer(new CapturedImage(imageName, imageData),
                                                    100, TimeUnit.MILLISECONDS);
                                            if (added) {
                                                LOGGER.info("成功放入队列: " + imageName);
                                            } else {
                                                // 理论上不会走到这里，因为已确保队列有容量
                                                LOGGER.warning("意外：队列仍满，无法加入新帧: " + imageName);
                                            }
                                        }
                                        // =======================================================

                                        // 重置状态，准备收集下一帧
                                        imageBuffer.reset();
                                        isCollecting = false;
                                        i++;  // 跳过结束标记的第二个字节
                                        break;  // 跳出循环，等待下一次读取
                                    }
                                }
                            }
                        }
                    }

                    LOGGER.info("停止从FFmpeg读取输出流，共捕获 " + frameCount + " 帧");
                }

                // 等待FFmpeg进程结束，获取退出码
                int exitCode = ffmpegProcess.waitFor();
                LOGGER.info("FFmpeg进程结束，退出码: " + exitCode);  // 0表示正常结束

            } catch (InterruptedException e) {  // 线程被中断（如系统关闭）
                Thread.currentThread().interrupt();  // 保留中断状态
                LOGGER.info("截图线程被中断");
                break;  // 退出重试循环
            } catch (IOException e) {  // IO错误（如视频流无法访问、FFmpeg启动失败）
                LOGGER.log(Level.SEVERE, "截图过程IO错误", e);
            } finally {
                // 增加重试间隔控制，避免频繁重启
                long currentTime = System.currentTimeMillis();
                if (currentTime - lastRetryTime < MIN_RETRY_INTERVAL) {
                    try {
                        Thread.sleep(MIN_RETRY_INTERVAL - (currentTime - lastRetryTime));
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        break;  // 退出重试循环
                    }
                }
                lastRetryTime = currentTime;
                retryCount++;

                // 无论是否异常，确保FFmpeg进程被销毁
                if (ffmpegProcess != null && ffmpegProcess.isAlive()) {
                    LOGGER.info("强制终止FFmpeg进程");
                    ffmpegProcess.destroyForcibly();  // 强制销毁进程
                }

                if (retryCount < maxRetries) {
                    LOGGER.info("准备重试FFmpeg，剩余尝试次数: " + (maxRetries - retryCount));
                } else {
                    LOGGER.severe("达到最大重试次数，停止尝试");
                }
            }
        }

        if (retryCount >= maxRetries) {
            LOGGER.severe("视频捕获失败：达到最大重试次数");
        }
    }

//    private void saveImages() {
//        try {
//            // 循环从队列取图像，直到线程被中断
//            while (!Thread.currentThread().isInterrupted()) {
//                CapturedImage image = imageQueue.take();  // 阻塞等待，直到获取图像
//                // 构建保存路径（输出目录 + 图像名称）
//                Path filePath = Paths.get(outputDir, image.getName());
//                // 将图像字节数据写入文件
//                Thread.sleep(200);
//                Files.write(filePath, image.getData());
//                LOGGER.info("保存图像成功: " + filePath);  // 记录成功日志
//            }
//        } catch (InterruptedException e) {  // 线程被中断
//            Thread.currentThread().interrupt();
//            LOGGER.info("保存线程被中断");
//        } catch (IOException e) {  // IO错误（如目录不可写、磁盘满）
//            LOGGER.log(Level.SEVERE, "图像保存失败", e);
//        }
//    }

    // 使用线程池管理异步写入
    private final ExecutorService writeExecutor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
    private final Set<Future<?>> pendingWrites = ConcurrentHashMap.newKeySet();

    /**
     * 从队列消费图片，调用AI生成文本，并写入结果文件
     */
    private void processImagesWithAI() {
        // 用于跟踪未完成的AI任务（确保关闭时等待完成）
        Set<Future<?>> pendingAiTasks = ConcurrentHashMap.newKeySet();

        try {
            while (!Thread.currentThread().isInterrupted()) {
                // 从队列阻塞获取图片（若队列空则等待）
                CapturedImage image = imageQueue.take();
                LOGGER.info("开始处理图片: " + image.getName());

                // 提交AI处理任务到线程池（异步处理，避免阻塞消费）
                Future<?> aiTask = threadPoolExecutor.submit(() -> {
                    try {
                        // 调用AI生成文本
                        String aiResult = generateTextFromImage(image);
                        // 写入结果到文件
                        writeAiResultToFile(image.getName(), aiResult);
                        LOGGER.info("图片处理完成: " + image.getName());
                    } catch (Exception e) {
                        LOGGER.log(Level.SEVERE, "处理图片" + image.getName() + "失败", e);
                    }
                });
                pendingAiTasks.add(aiTask);

                // 清理已完成的任务（避免内存泄漏）
                pendingAiTasks.removeIf(Future::isDone);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            LOGGER.info("AI处理线程被中断");
        } finally {
            // 等待剩余AI任务完成
            waitForPendingAiTasks(pendingAiTasks);
        }
    }

    /**
     * 调用AI模型从图片生成文本
     */
    private String generateTextFromImage(CapturedImage image) throws Exception {
        try {
            // 1. 将图片byte[]转为Resource（用于AI模型输入）
            ByteArrayResource imageResource = new ByteArrayResource(image.getData()) {
                @Override
                public String getFilename() {
                    return image.getName(); // 保留图片名称
                }
            };

            // 2. 构建多模态用户消息（图片+提示词）
            UserMessage userMessage = UserMessage.builder()
                    .text("请详细描述这张图片的内容，包括场景、物体和细节") // 可自定义提示词
                    .media(new Media(MimeType.valueOf(MimeTypeUtils.IMAGE_PNG_VALUE), imageResource)) // 图片类型为JPEG
                    .build();

            // 3. 构建提示词（指定LLAVA模型）
            Prompt prompt = new Prompt(
                    userMessage,
                    OllamaOptions.builder().model(OllamaModel.LLAVA).build()
            );

            // 4. 调用AI模型
            ChatResponse response = ollamaChatModel.call(prompt);
            return response.getResult().getOutput().getText();
        } catch (Exception e) {
            LOGGER.log(Level.SEVERE, "AI调用失败（图片：" + image.getName() + "）", e);
            throw new RuntimeException("AI处理图片失败", e);
        }
    }

    /**
     * 将AI结果写入配置文件（格式：图片名称 == 输出内容）
     */
    private void writeAiResultToFile(String imageName, String aiResult) throws IOException {
        // 构建输出行（替换换行符，避免格式错乱）
        String cleanResult = aiResult.replace("\n", " ").replace("\r", " ");
        String outputLine = imageName + " == " + cleanResult + System.lineSeparator();

        // 追加写入文件（使用UTF-8编码，确保中文正常）
        Files.write(
                Paths.get(aiResultFile),
                outputLine.getBytes(StandardCharsets.UTF_8),
                StandardOpenOption.APPEND
        );
    }

    // 等待未完成的AI任务（关闭时调用）
    private void waitForPendingAiTasks(Set<Future<?>> pendingTasks) {
        if (pendingTasks.isEmpty()) {
            return;
        }
        LOGGER.info("等待" + pendingTasks.size() + "个未完成的AI任务...");
        pendingTasks.forEach(task -> {
            try {
                task.get(30, TimeUnit.SECONDS); // 最多等待30秒
            } catch (Exception e) {
                LOGGER.log(Level.WARNING, "AI任务超时或失败", e);
            }
        });
    }

    private void saveImages() {
        while (!Thread.currentThread().isInterrupted()) {
            try {
                CapturedImage image = imageQueue.take();
                Path filePath = Paths.get(outputDir, image.getName());

                // 提交写入任务
                Future<?> future = writeExecutor.submit(() -> {
                    try {
                        try {
                            Thread.sleep(3000);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                        Files.write(filePath, image.getData());
                        LOGGER.info("保存成功: " + filePath);
                    } catch (IOException e) {
                        LOGGER.log(Level.SEVERE, "保存失败: " + filePath, e);
                    }
                });

                pendingWrites.add(future);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }


    // 新增计数器，跟踪未完成的异步写入任务
    private final AtomicInteger unfinishedWrites = new AtomicInteger(0);

    //    private void saveImages() {
//        // 使用异步文件通道提升写入效率
//        AsynchronousFileChannel asyncChannel = null;
//        try {
//            CapturedImage oldTake = imageQueue.poll();  // 丢弃第一个元素
//            // 循环从队列取图像，直到线程被中断
//            while (!Thread.currentThread().isInterrupted()) {
//                CapturedImage image = imageQueue.take();  // 阻塞等待，直到获取图像
//                // 构建保存路径（输出目录 + 图像名称）
//                Path filePath = Paths.get(outputDir, image.getName());
//
//                // 异步写入文件（非阻塞）
//                asyncChannel = AsynchronousFileChannel.open(
//                        filePath, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
//                ByteBuffer buffer = ByteBuffer.wrap(image.getData());
//
//                // 异步回调处理结果
//                AsynchronousFileChannel finalAsyncChannel = asyncChannel;
//
//                // 睡一觉 模拟方法耗时
//                Thread.sleep(3000);
//                asyncChannel.write(buffer, 0, null, new CompletionHandler<Integer, Void>() {
//                    @Override
//                    public void completed(Integer bytesWritten, Void attachment) {
//                        LOGGER.info("保存图像成功: " + filePath + "（" + bytesWritten + "字节）");
//                        try {
//                            finalAsyncChannel.close();
//                        } catch (IOException e) {
//                            LOGGER.log(Level.WARNING, "关闭文件通道失败", e);
//                        }
//                    }
//
//                    @Override
//                    public void failed(Throwable exc, Void attachment) {
//                        LOGGER.log(Level.SEVERE, "图像保存失败: " + filePath, exc);
//                    }
//                });
//            }
//        } catch (InterruptedException | IOException e) {  // 线程被中断
//            Thread.currentThread().interrupt();
//            LOGGER.info("保存线程被中断");
//        } finally {
//            if (asyncChannel != null) {
//                try {
//                    asyncChannel.close();
//                } catch (IOException e) {
//                    LOGGER.log(Level.WARNING, "关闭异步通道失败", e);
//                }
//            }
//        }
//    }
    private void logStatus() {
        try {
            // 循环打印队列状态，直到线程被中断
            while (!Thread.currentThread().isInterrupted()) {
                // 输出当前队列大小和剩余容量（监控队列是否拥堵）
                LOGGER.info("队列状态: " + imageQueue.size() + "/" + imageQueue.remainingCapacity());
                Thread.sleep(5000);  // 每5秒打印一次
            }
        } catch (InterruptedException e) {  // 线程被中断
            Thread.currentThread().interrupt();
            LOGGER.info("日志线程被中断");
        }
    }

    /**
     * 系统关闭方法
     */
    public void shutdown() {
        // 1. 先优雅关闭FFmpeg（发送中断信号，等待其结束）
        if (ffmpegProcess != null && ffmpegProcess.isAlive()) {
            LOGGER.info("尝试优雅关闭FFmpeg...");
            ffmpegProcess.destroy(); // 发送正常终止信号（而非强制销毁）
            try {
                // 等待FFmpeg完成当前帧并退出（最多等待5秒）
                if (ffmpegProcess.waitFor(5, TimeUnit.SECONDS)) {
                    LOGGER.info("FFmpeg正常退出，退出码: " + ffmpegProcess.exitValue());
                } else {
                    // 若5秒后仍未退出，再强制销毁（作为兜底）
                    LOGGER.warning("FFmpeg未及时退出，强制销毁");
                    ffmpegProcess.destroyForcibly();
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }

        // 2. 先等待队列清空，再等待异步写入完成
        long waitTime = 20000;
        long start = System.currentTimeMillis();
        // 等待队列清空
        while (!imageQueue.isEmpty() && (System.currentTimeMillis() - start) < waitTime) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        // 等待所有异步写入完成
        start = System.currentTimeMillis();
        while (unfinishedWrites.get() > 0 && (System.currentTimeMillis() - start) < waitTime) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        // 3. 关闭写入线程池并等待任务完成
        writeExecutor.shutdown();
        try {
            if (!writeExecutor.awaitTermination(10, TimeUnit.SECONDS)) {
                LOGGER.warning("强制终止写入线程");
                writeExecutor.shutdownNow();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }


        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        // 4. 关闭自定义线程池
        threadPoolExecutor.shutdownNow();
        try {
            if (!threadPoolExecutor.awaitTermination(3, TimeUnit.SECONDS)) {
                LOGGER.warning("线程池未能及时关闭");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            LOGGER.warning("关闭过程被中断");
        }
    }

    @Override
    public void videoCaptureSystemDemo() {
        initOutputDirectory();
        start();
        // 示例：运行30秒后关闭（实际可根据业务需求调整）
//        try {
//            Thread.sleep(30000);
//        } catch (InterruptedException e) {
//            Thread.currentThread().interrupt();
//        } finally {
//            shutdown();
//        }
    }

    // 图像数据封装类
    private static class CapturedImage {
        private final String name;
        private final byte[] data;

        public CapturedImage(String name, byte[] data) {
            this.name = name;
            this.data = data;
        }

        public String getName() {
            return name;
        }

        public byte[] getData() {
            return data;
        }
    }


    public long calculateFullQueueMemory() {
        // 1. 计算空队列结构内存
        LinkedBlockingQueue<CapturedImage> emptyQueue =
                new LinkedBlockingQueue<>(imageQueue.remainingCapacity() + imageQueue.size());
        long queueStructureSize = GraphLayout.parseInstance(emptyQueue).totalSize();

        // 2. 计算单个元素内存
        CapturedImage sample = new CapturedImage("sample.png", new byte[1024]);
        long perElementSize = calculateElementSize(sample);

        // 3. 总内存 = 结构内存 + (元素大小 × 容量)
        return queueStructureSize + (perElementSize * imageQueue.remainingCapacity() + imageQueue.size());
    }

    private long calculateElementSize(CapturedImage image) {
        // 计算对象图总大小（包含嵌套的String/byte[]）
        return GraphLayout.parseInstance(image).totalSize();
    }

    public long calculateActualQueueMemory() {
        synchronized (imageQueue) {
            // 关键：使用 GraphLayout 遍历整个对象图
            return GraphLayout.parseInstance(imageQueue).totalSize();
        }
    }
}
